/*
 * Copyright 2010 Alessandro Bellucci
 * 
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */
package org.soupframework.gwt.designerpanel.client.designer.impl;

import java.util.HashMap;
import java.util.Map;
import java.util.Stack;

import org.soupframework.gwt.designerpanel.client.DesignerPanel;
import org.soupframework.gwt.designerpanel.client.command.Command;
import org.soupframework.gwt.designerpanel.client.command.CommandFactory;
import org.soupframework.gwt.designerpanel.client.command.CommandGroup;
import org.soupframework.gwt.designerpanel.client.decorator.ObjectDecorator;
import org.soupframework.gwt.designerpanel.client.designer.ContainerDesigner;
import org.soupframework.gwt.designerpanel.client.designer.ContextMenuFactory;
import org.soupframework.gwt.designerpanel.client.designer.Designer;
import org.soupframework.gwt.designerpanel.client.designer.DesignerManager;
import org.soupframework.gwt.designerpanel.client.designer.OperatingMode;
import org.soupframework.gwt.designerpanel.client.dragproxy.DragProxy;
import org.soupframework.gwt.designerpanel.client.dragproxy.DragProxyFactory;
import org.soupframework.gwt.designerpanel.client.grid.GridMode;

/**
 * Out of the box implementation for {@link DesignerManager}
 *
 * @param <T> represents the base type of the objects being designed
 */
public abstract class DesignerManagerBase<T> implements DesignerManager<T> {

	/**
	 * The {@link DesignerPanel}
	 */
	private DesignerPanel<T> designerPanel;

	/**
	 * The root {@link Designer}
	 */
	private Designer<T> rootDesigner;

	/**
	 * Operating mode for this DesignerPanel
	 */
	private OperatingMode operatingMode = OperatingMode.DESIGN_MODE;

	/**
	 * Map of all {@link Designer}
	 */
	protected Map<T, Designer<T>> designers = new HashMap<T, Designer<T>>();

	private CommandFactory<T> commandFactory;

	private Stack<Command> commandHistoryDone;
	private Stack<Command> commandHistoryUndone;

	/**
	 * Object highlighter is used to highlight selected objects
	 */
	private ObjectDecorator<T> objectHighlighter;

	/**
	 * Container decorator is used to highlight containers in Design Mode
	 */
	private ObjectDecorator<T> containerDesignModeDecorator;

	/**
	 * Factory for {@link DragProxy}
	 */
	private DragProxyFactory<T> dragProxyFactory;

	/**
	 * Resize points decorator is used to show resize drag points
	 */
	private ObjectDecorator<T> resizePointsDecorator;

	/**
	 * Current {@link CommandGroup}
	 */
	private CommandGroup currentCommandGroup;

	/**
	 * Store the grid visibility
	 */
	private boolean showGrid;

	/**
	 * The grid mode
	 */
	private GridMode gridMode;

	/**
	 * The context menu factory
	 */
	private ContextMenuFactory<T> contextMenuFactory;

	public DesignerManagerBase(DesignerPanel<T> designerPanel){
		this.designerPanel = designerPanel;
		this.rootDesigner = design(null, designerPanel.getRootObject());
	}

	protected abstract Designer<T> design(ContainerDesigner<T> parent, T object);

	@Override
	public DesignerPanel<T> getDesignerPanel(){
		return this.designerPanel;
	}

	@Override
	public OperatingMode getOperatingMode() {
		return this.operatingMode;
	}

	@Override
	public void setOperatingMode(OperatingMode operatingMode) {
		this.operatingMode = operatingMode;
		this.refreshDesigners();
	}

	@Override
	public void refreshDesigners(){
		if(operatingMode == OperatingMode.LIVE_MODE){
			if(this.rootDesigner instanceof ContainerDesigner<?>){
				((ContainerDesigner<T>)this.rootDesigner).clearSelection(false);
			}
			this.designerPanel.hideGrid();
		}
		else{
			if(this.showGrid){
				this.designerPanel.showGrid();
			}
		}

		if(operatingMode == OperatingMode.DESIGN_MODE){
			this.designerPanel.enableGlassPanel();
		}
		else{
			this.designerPanel.disableGlassPanel();
		}

		this.getRootDesigner().refresh();
	}

	@Override
	public Designer<T> getRootDesigner() {
		return this.rootDesigner;
	}

	@Override
	public void setCommandFactory(CommandFactory<T> commandFactory){
		this.commandFactory = commandFactory;
	}

	@Override
	public CommandFactory<T> getCommandFactory(){
		return this.commandFactory;
	}

	@Override
	public void setContextMenuFactory(ContextMenuFactory<T> contextMenuFactory){
		this.contextMenuFactory = contextMenuFactory;
	}

	@Override
	public ContextMenuFactory<T> getContextMenuFactory(){
		return this.contextMenuFactory;
	}

	@Override
	public void enableHistory() {
		if(commandHistoryDone != null){
			throw new IllegalStateException("History is already enabled");
		}
		this.commandHistoryDone = new Stack<Command>();
		this.commandHistoryUndone = new Stack<Command>();
	}

	@Override
	public void disableHistory() {
		if(this.commandHistoryDone == null){
			throw new IllegalStateException("History is not enabled");
		}
		this.commandHistoryDone.clear();
		this.commandHistoryDone = null;
		this.commandHistoryUndone.clear();
		this.commandHistoryUndone = null;
	}

	@Override
	public int getHistorySize() {
		if(this.commandHistoryDone == null){
			throw new IllegalStateException("History is not enabled");
		}
		return this.commandHistoryDone.size();
	}

	@Override
	public Command peekLastCommandDone(){
		if(this.commandHistoryDone.size() > 0){
			return this.commandHistoryDone.peek();
		}
		return null;
	}

	@Override
	public void doCommand(Command command){
		command.doCommand();
		if(commandHistoryDone != null){
			if(this.currentCommandGroup != null){
				this.currentCommandGroup.addCommand(command);
			}
			else{
				this.commandHistoryDone.push(command);
			}
			this.commandHistoryUndone.clear();
		}
	}

	@Override
	public void undo() {
		if(this.commandHistoryDone == null){
			throw new IllegalStateException("History is not enabled");
		}
		if(this.commandHistoryDone.size() == 0){
			throw new IllegalStateException("No commands to undo");
		}
		Command command = this.commandHistoryDone.peek();
		command.undoCommand();
		this.commandHistoryDone.pop();
		this.commandHistoryUndone.push(command);
	}

	@Override
	public void redo(){
		if(this.commandHistoryDone == null){
			throw new IllegalStateException("History is not enabled");
		}
		if(this.commandHistoryUndone.size() == 0){
			throw new IllegalStateException("No commands to redo");
		}
		Command command = this.commandHistoryUndone.peek();
		command.doCommand();
		this.commandHistoryUndone.pop();
		this.commandHistoryDone.push(command);
	}

	@Override
	public void doPartialCommand(String commandGroupTypeName, Command command){
		if(commandHistoryDone != null){
			Command lastCommandDone = this.peekLastCommandDone();
			if(lastCommandDone instanceof CommandGroup && commandGroupTypeName.equals(((CommandGroup)lastCommandDone).getType())){
				command.doCommand();

				CommandGroup commandGroup = (CommandGroup)lastCommandDone;
				commandGroup.addCommand(command);
			}
			else{
				CommandGroup commandGroup = this.getCommandFactory().createCommandGroup(commandGroupTypeName);
				commandGroup.addCommand(command);
				commandGroup.doCommand();

				commandHistoryDone.push(commandGroup);
			}
		}
		else{
			command.doCommand();
		}
	}

	@Override
	public void beginCommandGroup(String commandGroupTypeName){
		if(commandHistoryDone != null){
			if(this.currentCommandGroup != null){
				throw new IllegalStateException("A command group is already active");
			}

			this.currentCommandGroup = this.getCommandFactory().createCommandGroup(commandGroupTypeName);
			// put the command group in the done state
			this.currentCommandGroup.doCommand();
			// put the current command group in history
			this.commandHistoryDone.push(this.currentCommandGroup);
		}
	}

	@Override
	public void endCommandGroup(String commandGroupTypeName){
		if(commandHistoryDone != null){
			if(this.currentCommandGroup == null){
				throw new IllegalStateException("No active command groups");
			}
			if(!commandGroupTypeName.equals(this.currentCommandGroup.getType())){
				throw new IllegalStateException("Active command group has a different type name: " + this.currentCommandGroup.getType() + " insteads of " + commandGroupTypeName);
			}

			this.currentCommandGroup = null;
		}
	}

	/**
	 * Shows the grid
	 */
	public void showGrid(){
		this.showGrid = true;
		if(this.getOperatingMode() == OperatingMode.DESIGN_MODE || this.getOperatingMode() == OperatingMode.LIVE_DESIGN_MODE){
			this.designerPanel.showGrid();
		}
	}

	/**
	 * Hides the grid
	 */
	public void hideGrid(){
		this.showGrid = false;
		this.designerPanel.hideGrid();
	}

	@Override
	public GridMode getGridMode(){
		return this.gridMode;
	}

	@Override
	public void setGridMode(GridMode gridMode){
		this.gridMode = gridMode;
	}

	@Override
	public ObjectDecorator<T> getObjectHighlighter(){
		return this.objectHighlighter;
	}

	@Override
	public void setObjectHighlighter(ObjectDecorator<T> objectHighligher){
		this.objectHighlighter = objectHighligher;
	}

	@Override
	public ObjectDecorator<T> getContainerDesignModeDecorator(){
		return this.containerDesignModeDecorator;
	}

	@Override
	public void setContainerDesignModeDecorator(ObjectDecorator<T> containerDesignModeDecorator){
		this.containerDesignModeDecorator = containerDesignModeDecorator;
	}

	public DragProxyFactory<T> getDragProxyFactory(){
		return this.dragProxyFactory;
	}

	@Override
	public void setDragProxyFactory(DragProxyFactory<T> dragProxyFactory){
		this.dragProxyFactory = dragProxyFactory;
	}

	@Override
	public ObjectDecorator<T> getResizePointsDecorator(){
		return this.resizePointsDecorator;
	}

	@Override
	public void setResizePointsDecorator(ObjectDecorator<T> resizePointsDecorator){
		this.resizePointsDecorator = resizePointsDecorator;
	}
}
